﻿using System;
using System.Collections.Generic;
using Microsoft.ApplicationServer.Caching;
using System.Threading;
using System.IO;

namespace CacheHandlerManager
{
    //all cache handling for ASNet goes through here
    public class CacheHandler
    {
        private DataCache _cache;
        private Dictionary<string, DataCacheLockHandle> locks;
        private string hostName, cachePartition;
        private int cachePort;

        public CacheHandler(string hostName, int cachePort, string cachePartition)
        {
            this.locks = new Dictionary<string, DataCacheLockHandle>();
            this.hostName = hostName;
            this.cachePartition = cachePartition;
            this.cachePort = cachePort;

            initialiseCache();
        }

        private void initialiseCache()
        {
            DataCacheFactory _factory;
            List<DataCacheServerEndpoint> servers = new List<DataCacheServerEndpoint>(1);
            servers.Add(new DataCacheServerEndpoint(hostName, cachePort));
            DataCacheFactoryConfiguration config = new DataCacheFactoryConfiguration();
            config.Servers = servers;
            config.LocalCacheProperties = new DataCacheLocalCacheProperties();
            DataCacheClientLogManager.ChangeLogLevel(System.Diagnostics.TraceLevel.Off);

            _factory = new DataCacheFactory(config);
            _cache = _factory.GetCache(cachePartition);
            _cache.CreateRegion(cachePartition);
        }

        public object getWithLock(string key, int lockTime)
        {
            bool success = false;
            TimeSpan interval = new TimeSpan(0, 0, lockTime);
            DataCacheLockHandle lHandle;
            object r = null;

            //busy wait
            while (!success)
            {
                int retries = 10;

                while (retries > 0)
                {
                    try
                    {
                        r = _cache.GetAndLock(key, interval, out lHandle, cachePartition);
                        locks.Add(key, lHandle);

                        return r;
                    }
                    catch (DataCacheException e)
                    {
                        if (e.ErrorCode != DataCacheErrorCode.ObjectLocked)
                        {
                            if (e.ErrorCode == DataCacheErrorCode.KeyDoesNotExist)
                            {
                                return null;
                            }
                            else
                            {
                                if (--retries == 0)
                                {
                                    logMessage(e.Message, 1);
                                    logMessage(e.StackTrace, 1);
                                    
                                    return null;
                                }

                                Thread.Sleep(6000);
                            }
                        };
                    }
                }
            }

            return null;
        }

        public void putWithUnlock(string key, object val)
        {
            DataCacheLockHandle lHandle;
            int retries = 10;

            while (retries > 0)
            {
                try
                {
                    if (locks.TryGetValue(key, out lHandle))
                    {
                        _cache.PutAndUnlock(key, val, lHandle, cachePartition);
                        locks.Remove(key);
                        retries = 0;
                    }
                    else
                    {
                        _cache.Put(key, val, cachePartition);
                        retries = 0;
                    }
                }
                catch (DataCacheException e)
                {
                    if (--retries == 0)
                    {
                        logMessage(e.Message, 1);
                        logMessage(e.StackTrace, 1);

                        if (locks.TryGetValue(key, out lHandle))
                        {
                            locks.Remove(key);
                            putWithNoLock(key, val);
                        }
                    }

                    Thread.Sleep(6000);
                }
            }
        }

        public object getWithNoLock(string key)
        {
            int retries = 10;
            object retStr = null;
            while (retries > 0)
            {
                try
                {
                    retStr = _cache.Get(key, cachePartition);
                    retries = 0;

                    return retStr;
                }
                catch (DataCacheException e)
                {
                    if (--retries == 0)
                    {
                        logMessage(e.Message, 1);
                        logMessage(e.StackTrace, 1);
                    }
                }

                Thread.Sleep(6000);
            }

            return retStr;
        }

        public object putWithNoLock(string key, object val)
        {
            int retries = 10;
            object retStr = null;
            while (retries > 0)
            {
                try
                {
                    retStr =_cache.Put(key, val, cachePartition);
                    retries = 0;

                    return retStr;
                }
                catch (DataCacheException e)
                {
                    if (--retries == 0)
                    {
                        logMessage(e.Message, 1);
                        logMessage(e.StackTrace, 1);
                    }
                }

                Thread.Sleep(6000);
            }

            return retStr;
        }

        public void removeFromCache(string key)
        {
            int retries = 10;
            while (retries > 0)
            {
                try
                {
                    _cache.Remove(key, cachePartition);
                    retries = 0;
                }
                catch (DataCacheException e)
                {
                    if (--retries == 0)
                    {
                        logMessage(e.Message, 1);
                        logMessage(e.StackTrace, 1);
                    }
                }

                Thread.Sleep(6000);
            }
        }

        public IEnumerable<KeyValuePair<string, object>> getAllObjectsInRegion(string region)
        {
            int retries = 10;
            IEnumerable<KeyValuePair<string, object>> retStr;
            while (retries > 0)
            {
                try
                {
                    retStr = _cache.GetObjectsInRegion(region);
                    retries = 0;

                    return retStr;
                }
                catch (DataCacheException e)
                {
                    if (--retries == 0)
                    {
                        logMessage(e.Message, 1);
                        logMessage(e.StackTrace, 1);
                    }
                }

                Thread.Sleep(6000);
            }

            return _cache.GetObjectsInRegion(region);
        }

        public void unlock(string key)
        {
            DataCacheLockHandle lHandle;
            
            int retries = 10;
            while (retries > 0)
            {
                try
                {
                    if (locks.TryGetValue(key, out lHandle))
                    {
                        _cache.Unlock(key, lHandle, cachePartition);
                        retries = 0;
                    }
                    else
                    {
                        throw (new DataCacheException());
                    }
                }
                catch (DataCacheException e)
                {
                    if (--retries == 0)
                    {
                        logMessage(e.Message, 1);
                        logMessage(e.StackTrace, 1);

                    }
                }

                Thread.Sleep(6000);
            }
        }

        private void logMessage(string message, int errorLevel)
        {
            String errorLevelStr = null;

            switch (errorLevel)
            {
                case 1:
                    errorLevelStr = "ERROR";
                    break;
                case 2:
                    errorLevelStr = "WARNING";
                    break;
                default:
                    errorLevelStr = "INFO";
                    break;
            }
            StreamWriter sw = new StreamWriter("C:\\ASNet\\asnet.log", true);
            sw.WriteLine(DateTime.Now.ToString(errorLevelStr + " " + String.Format("yyyy/MM/dd HH:mm:ss")) + ": " + message); 
            sw.Close();
        }
    }
}
